package alphas.a2.util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.StringTokenizer;

public class LogClientAPI implements ILogClientAPI {

        public final static int DEFAULT_PORT = 30123;

	private final static String EVTYPE = "evtype";
	private final static String USERNAME = "user";
	private final static String PASSWORD = "pass";
	private final static String ORDER_ID = "order_id";
	private final static String TS = "ts";
	private final static String EVTYPE_LOGIN = "login";
	private final static String EVTYPE_LOGOUT = "logout";
	private final static String EVTYPE_ADDORDER = "add_order";
	private final static String EVTYPE_SHIPPED = "shipped";

	private final static SimpleDateFormat sdf = new SimpleDateFormat(
			"yyyyMMddHHmmss");

	private Socket logAPIclientSocket;
	private BufferedWriter wr;
	private BufferedReader rd;
	
	private int port;

	/**
	 * Creates an instance of LogClientAPI.
	 * 
	 * @param host
	 *            the host of the server to send the logs
	 * @param port
	 *            the port of the server host to send the logs
	 * @throws Exception
	 *             Error connecting to the server
	 */
	public LogClientAPI(String host, int port) throws Exception {
		this.port = port;
		//connects to the server socket
		this.createSocket(host, port);
		//open output stream to write on socket
		wr = new BufferedWriter(new OutputStreamWriter(
				logAPIclientSocket.getOutputStream()));
		//open input stream to read from the socket
		rd = new BufferedReader(new InputStreamReader(
				logAPIclientSocket.getInputStream()));
	}
	

	/**
	 * Creates a client socket to the logs server
	 * 
	 * @param host
	 *            the host of the log server
	 * @param port
	 *            the port of the log server
	 * @throws Exception
	 *             Error connecting to the server host and port.
	 */
	private void createSocket(String host, int port) throws Exception {
		// Create a socket with a timeout
		try {
			InetAddress addr = InetAddress.getByName(host);
			SocketAddress sockaddr = new InetSocketAddress(addr, port);

			// Create an unbound socket
			logAPIclientSocket = new Socket();

			// This method will block no more than timeoutMs.
			// If the timeout occurs, SocketTimeoutException is thrown.
			int timeoutMs = 2000; // 2 seconds
			logAPIclientSocket.connect(sockaddr, timeoutMs);
		} catch (UnknownHostException e) {
			System.out.println("UnknownHostException: " + e);
		} catch (SocketTimeoutException e) {
			System.out.println("SocketTimeoutException: " + e);
		} catch (IOException e) {
			System.out.println("IOException: " + e);
		}
	}

	@Override
	public String login(String username, String password) {
		StringBuffer sb = new StringBuffer();

		// composing the message accordingly to the protocol. <key1=value1, ...,
		// keyN=valueN\n>
		// username and password are encoded because can contain keywords "="
		// and ","
		try {
			sb.append(LogClientAPI.EVTYPE);
			sb.append("=");
			sb.append(LogClientAPI.EVTYPE_LOGIN);
			sb.append(",");
			sb.append(LogClientAPI.TS);
			sb.append("=");
			sb.append(LogClientAPI.sdf.format(new Date()));
			sb.append(",");
			sb.append(LogClientAPI.USERNAME);
			sb.append("=");
			sb.append(URLEncoder.encode(username, "UTF-8"));
			sb.append(",");
			sb.append(LogClientAPI.PASSWORD);
			sb.append("=");
			sb.append(URLEncoder.encode(password, "UTF-8"));
			sb.append("\n");
		} catch (UnsupportedEncodingException e) {
			return "NOK. Login UnsupportedEncodingException in username/password \n"
					+ e.getMessage();
		}

		// sending the log message to the server and returning appropriate
		// response
		return this.sendLogMessage(sb.toString());
	}

	@Override
	public String logout(String username, String password) {
		StringBuffer sb = new StringBuffer();

		// composing the message accordingly to the protocol. <key1=value1, ...,
		// keyN=valueN\n>
		// username and password are encoded because can contain keywords "="
		// and ","
		try {
			sb.append(LogClientAPI.EVTYPE);
			sb.append("=");
			sb.append(LogClientAPI.EVTYPE_LOGOUT);
			sb.append(",");
			sb.append(LogClientAPI.TS);
			sb.append("=");
			sb.append(LogClientAPI.sdf.format(new Date()));
			sb.append(",");
			sb.append(LogClientAPI.USERNAME);
			sb.append("=");
			sb.append(URLEncoder.encode(username, "UTF-8"));
			sb.append(",");
			sb.append(LogClientAPI.PASSWORD);
			sb.append("=");
			sb.append(URLEncoder.encode(password, "UTF-8"));
			sb.append("\n");
		} catch (UnsupportedEncodingException e) {
			return "NOK. Logout UnsupportedEncodingException in username/password \n"
					+ e.getMessage();
		}

		// sending the log message to the server and returning appropriate
		// response
		return this.sendLogMessage(sb.toString());
	}

	@Override
	public String addOrder(String username, String password, long orderID) {
		StringBuffer sb = new StringBuffer();

		// composing the message accordingly to the protocol. <key1=value1, ...,
		// keyN=valueN\n>
		// username and password are encoded because can contain keywords "="
		// and ","
		try {
			sb.append(LogClientAPI.EVTYPE);
			sb.append("=");
			sb.append(LogClientAPI.EVTYPE_ADDORDER);
			sb.append(",");
			sb.append(LogClientAPI.TS);
			sb.append("=");
			sb.append(LogClientAPI.sdf.format(new Date()));
			sb.append(",");
			sb.append(LogClientAPI.USERNAME);
			sb.append("=");
			sb.append(URLEncoder.encode(username, "UTF-8"));
			sb.append(",");
			sb.append(LogClientAPI.PASSWORD);
			sb.append("=");
			sb.append(URLEncoder.encode(password, "UTF-8"));
			sb.append(",");
			sb.append(LogClientAPI.ORDER_ID);
			sb.append("=");
			sb.append(orderID);
			sb.append("\n");
		} catch (UnsupportedEncodingException e) {
			return "NOK. Add Order UnsupportedEncodingException in username/password \n"
					+ e.getMessage();
		}

		// sending the log message to the server and returning appropriate
		// response
		return this.sendLogMessage(sb.toString());
	}

	@Override
	public String shipped(String username, String password, long orderID) {
		StringBuffer sb = new StringBuffer();

		// composing the message accordingly to the protocol. <key1=value1, ...,
		// keyN=valueN\n>
		// username and password are encoded because can contain keywords "="
		// and ","
		try {
			sb.append(LogClientAPI.EVTYPE);
			sb.append("=");
			sb.append(LogClientAPI.EVTYPE_SHIPPED);
			sb.append(",");
			sb.append(LogClientAPI.TS);
			sb.append("=");
			sb.append(LogClientAPI.sdf.format(new Date()));
			sb.append(",");
			sb.append(LogClientAPI.USERNAME);
			sb.append("=");
			sb.append(URLEncoder.encode(username, "UTF-8"));
			sb.append(",");
			sb.append(LogClientAPI.PASSWORD);
			sb.append("=");
			sb.append(URLEncoder.encode(password, "UTF-8"));
			sb.append(",");
			sb.append(LogClientAPI.ORDER_ID);
			sb.append("=");
			sb.append(orderID);
			sb.append("\n");

		} catch (UnsupportedEncodingException e) {
			return "NOK. Shipped UnsupportedEncodingException in username/password \n"
					+ e.getMessage();
		}
		
		// sending the log message to the server and returning appropriate
		// response
		return this.sendLogMessage(sb.toString());
	}
	

	@Override
	public void disconnect() {
		try {
			this.wr.close();
			this.rd.close();
			this.logAPIclientSocket.close();
		} catch (IOException e) {
			System.out.println("IOException closing socket: " + e);
			e.printStackTrace();
		}		
	}

	/**
	 * Sends the message log to the server
	 * 
	 * @param message
	 *            the message log to be sent to the server
	 * @return the response, corresponding to the success or failure of the
	 *         operation.
	 */
	private String sendLogMessage(String message) {
		String result = "";

		// send log
		try {			
			wr.write(message);
			wr.flush();
			System.out.println("REQUEST: " + message);
		} catch (IOException e) {
			System.out.println("IOException: " + e);
			return "NOK. Writing IOException\n" + e.getMessage();
		}

		// received response
		try {
			result = rd.readLine();
			System.out.println("RESPONSE: " + result);
			StringTokenizer st = new StringTokenizer(result, "=");
			if(!st.nextToken().equalsIgnoreCase("message")){
				return "NOK. Log Server is acting weird!! Maybe you are connected to another server using the port: " + port;
			}
			result = URLDecoder.decode(st.nextToken(), "UTF-8");
		} catch (IOException e) {
			System.out.println("IOException: " + e);
			return "NOK. Reading IOException\n" + e.getMessage();
		}

		return result;
	}

}
